package top.codecrab.eblog.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import top.codecrab.eblog.common.response.CommentCount;
import top.codecrab.eblog.common.response.R;
import top.codecrab.eblog.config.Constants;
import top.codecrab.eblog.dto.IpRegion;
import top.codecrab.eblog.entity.Category;
import top.codecrab.eblog.entity.Comment;
import top.codecrab.eblog.entity.Post;
import top.codecrab.eblog.entity.UserMessage;
import top.codecrab.eblog.mapper.CommentMapper;
import top.codecrab.eblog.mapper.PostMapper;
import top.codecrab.eblog.mapper.UserCollectionMapper;
import top.codecrab.eblog.mapper.UserMessageMapper;
import top.codecrab.eblog.search.mq.MqTypes;
import top.codecrab.eblog.service.CategoryService;
import top.codecrab.eblog.service.PostService;
import top.codecrab.eblog.shiro.AccountProfile;
import top.codecrab.eblog.utils.*;
import top.codecrab.eblog.vo.PostVo;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.List;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author codecrab
 * @since 2021-02-26
 */
@Service
@RequiredArgsConstructor
public class PostServiceImpl extends ServiceImpl<PostMapper, Post> implements PostService {

    private final AmqpUtil amqpUtil;

    private final RedisUtil redisUtil;

    private final CommentMapper commentMapper;

    private final UserCollectionMapper collectionMapper;

    private final UserMessageMapper userMessageMapper;

    private final CategoryService categoryService;

    private final HttpServletRequest request;

    @Override
    public IPage<PostVo> paging(Page<PostVo> page, Long categoryId, Long userId, Integer level, Boolean recommend, String order) {
        level = ObjectUtil.defaultIfNull(level, -1);
        order = ObjectUtil.defaultIfBlank(order, "created");

        QueryWrapper<Post> wrapper = new QueryWrapper<Post>()
            .eq(categoryId != null, "category_id", categoryId)
            .eq(userId != null, "user_id", userId)
            .eq(recommend != null, "recommend", recommend)
            .eq(level == 0, "level", 0)
            .gt(level > 0, "level", 0)
            // 查询已审核的
            .ge("p.status", 1)
            // 查询未删除的
            .eq("is_deleted", false)
            .orderByDesc(level > 0, "level")
            .orderByDesc(order);

        return baseMapper.selectPosts(page, wrapper);
    }

    @Override
    public PostVo detail(Long id) {
        Long profileId = ShiroUtil.getProfileId();
        Assert.notNull(profileId, "请先登录");

        PostVo postVo = baseMapper.selectOnePost(
            new QueryWrapper<Post>()
                .eq("p.id", id)
                .eq("is_deleted", false)
                .gt("p.status", 0)
        );

        if (postVo == null) {
            return null;
        }

        // 如果文章是未审核，并且登录用户不是文章作者，并且不是管理员
        if (profileId != null && postVo.getStatus() == 0 &&
            !postVo.getUserId().equals(profileId) &&
            !Constants.ADMIN_LIST.contains(profileId.toString())
        ) {
            return null;
        }

        return postVo;
    }

    @Override
    public void initWeekHot() {
        //编写sql 获取评论时间为上一周的时间点为条件查询
        QueryWrapper<Comment> wrapper = new QueryWrapper<Comment>()
            .ge("created", DateUtil.lastWeek())
            .ne("post_id", 0)
            .groupBy("post_id");

        List<CommentCount> commentCounts = commentMapper.selectPostCommentCount(wrapper);
        for (CommentCount count : commentCounts) {
            packageWeekHot(count, count.getPostId());
        }
    }

    @Override
    public void packageWeekHot(CommentCount count, Long postId) {
        redisUtil.zSet("week:hot:post", postId, count.getPostCommentCount());
        redisUtil.hset("week:hot:postInfo:" + postId, "id", postId);
        redisUtil.hset("week:hot:postInfo:" + postId, "title", count.getPostTitle());
        redisUtil.hset("week:hot:postInfo:" + postId, "postViewCount", count.getPostViewCount());
        //设置过期时间一周
        redisUtil.expire("week:hot:post", 7 * 24 * 60 * 60);
        redisUtil.expire("week:hot:postInfo:" + postId, 7 * 24 * 60 * 60);
    }

    /**
     * 重置每周热议
     */
    @Override
    public void resetWeekHot(Long postId) {
        redisUtil.del("week:hot:postInfo:" + postId, "week:hot:post");
        initWeekHot();
    }

    @Override
    public R saveOrUpdatePost(Post post) {
        boolean res;
        Long id;
        Post one = getById(post.getId());
        AccountProfile profile = ShiroUtil.getProfile();

        if (profile == null) {
            return R.fail("请先登录再尝试编辑").action("/login");
        }

        Long profileId = profile.getId();

        if (profileId == null) {
            return R.fail("请先登录再尝试编辑").action("/login");
        }

        IpRegion ipRegion = IpRegionUtil.searchRegionByXdb(ServletUtil.getClientIP(request));

        if (one == null) {
            //新增
            post.setViewCount(0);
            post.setEditMode("0");
            post.setCommentCount(0);
            post.setLevel(0);
            post.setRecommend(false);
            post.setCreated(new Date());
            post.setModified(post.getCreated());
            post.setVoteUp(0);
            post.setVoteDown(0);
            post.setUserId(profileId);
            if (profileId == 1) {
                //0表示未审核，1表示审核
                post.setStatus(1);
            } else {
                //0表示未审核，1表示审核
                post.setStatus(0);
            }
            if (ipRegion != null) {
                post.setIpRegion(StrUtil.blankToDefault(ipRegion.getCity(), ipRegion.getProvince()));
            }
            res = save(post);
            id = post.getId();

            //该分类下文章个数加1
            Category category = categoryService.getById(post.getCategoryId());
            Assert.notNull(category, "找不到分类");
            category.setPostCount(category.getPostCount() + 1);
            categoryService.updateById(category);
        } else {
            //编辑
            Assert.isTrue(one.getUserId().equals(profileId), "您没有权限修改不属于您的文章");
            // 使用新对象
            Post newPost = new Post();
            newPost.setId(one.getId());
            newPost.setModified(new Date());
            newPost.setTitle(post.getTitle());
            newPost.setCategoryId(post.getCategoryId());
            newPost.setContent(post.getContent());
            if (profileId != 1) {
                newPost.setStatus(0);
            }
            if (ipRegion != null) {
                newPost.setIpRegion(StrUtil.blankToDefault(ipRegion.getCity(), ipRegion.getProvince()));
            }
            res = updateById(newPost);
            id = newPost.getId();
        }

        //发送消息到队列
        amqpUtil.sendMsg(MqTypes.POST_INSERT_ROUTING_KEY, id);

        return res ?
            R.ok((one == null ? "发布" : "编辑") + "成功，请等待审核").action("/post/" + id) :
            R.fail("未知原因，发布失败");
    }

    @Override
    public IPage<PostVo> getPostList(Page<PostVo> page, String keyword, Boolean recommend, String order) {
        return baseMapper.selectPagePostList(page, keyword, recommend, order);
    }

    /**
     * 把阅读量缓存到redis中
     */
    @Override
    public void cacheViewCount(PostVo postVo, HttpServletRequest request) {
        String ip = CommonUtils.getRemoteHost(request);
        String key = "post:viewCount:" + postVo.getId();
        //如果缓存中存在该文章并且hash的键一样的，就直接return，不用增加
        if (redisUtil.hHasKey(key, ip)) {
            //显示假数据，使页面显示正常，不刷新缓存，等5分钟计时器更新
            postVo.setViewCount(postVo.getViewCount() + 1);
            return;
        }
        //根据不同id，hash的键也就不一样，最后就按照hash的长度来确定增加了多少访问量，过期时间：三个小时
        redisUtil.hset(key, ip, 1, 3 * 60 * 60);
        //同一个ip一天之内没访问过，访问量 + 1
        postVo.setViewCount(postVo.getViewCount() + 1);
    }

    /**
     * 分页查询用户收藏
     */
    @Override
    public IPage<PostVo> selectPageCollection(Page<PostVo> page, QueryWrapper<Post> wrapper) {
        return baseMapper.selectPageCollection(page, wrapper);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(Long id) {
        Post post = this.getById(id);
        Assert.notNull(post, "文章不存在或已被删除");

        Long profileId = ShiroUtil.getProfileId();
        if (profileId == null || !Constants.ADMIN_LIST.contains(profileId.toString())) {
            Assert.isTrue(post.getUserId().equals(profileId), "您没有权限删除不属于您的文章");
        }

        //逻辑删除文章
        this.removeById(id);

        //更新每周热议
        redisUtil.zSet("week:hot:post", id, 0);
        redisUtil.del("week:hot:postInfo:" + id);

        //该分类下文章个数减1
        Category category = categoryService.getById(post.getCategoryId());
        Assert.notNull(category, "找不到分类");

        category.setPostCount(Math.max(category.getPostCount() - 1, 0));
        categoryService.updateById(category);

        //删除关联的收藏
        collectionMapper.deleteByMap(MapUtil.of("post_id", id));

        //删除评论和消息
        commentMapper.delete(
            new QueryWrapper<Comment>()
                .eq("post_id", id)
        );

        userMessageMapper.delete(
            new QueryWrapper<UserMessage>()
                .eq("post_id", id)
        );

        //重置本周热议
        resetWeekHot(id);

        //发送消息到队列
        amqpUtil.sendMsg(MqTypes.POST_REMOVE_ROUTING_KEY, id);
    }

}
